package cn.kuporo.seckill.goods.application.cacahe.impl;

import cn.hutool.core.collection.CollUtil;
import cn.kuporo.seckill.common.cache.distribute.DistributedCacheService;
import cn.kuporo.seckill.common.cache.local.LocalCacheService;
import cn.kuporo.seckill.common.cache.model.SeckillBusinessCache;
import cn.kuporo.seckill.common.constants.RedisConstants;
import cn.kuporo.seckill.common.lock.DistributedLock;
import cn.kuporo.seckill.common.lock.factory.DistributedLockFactory;
import cn.kuporo.seckill.common.model.dto.SeckillStockDTO;
import cn.kuporo.seckill.common.model.enums.PlaceOrderType;
import cn.kuporo.seckill.common.utils.SystemClock;
import cn.kuporo.seckill.dubbo.interfaces.stock.SeckillStockDubboService;
import cn.kuporo.seckill.goods.application.cacahe.SeckillGoodsListCacheService;
import cn.kuporo.seckill.goods.domain.model.SeckillGoods;
import cn.kuporo.seckill.goods.domain.service.SeckillGoodsDomainService;
import com.alibaba.fastjson2.TypeReference;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * @author: kuporo
 * @since: 2025/7/9
 * @description:
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SeckillGoodsListCacheServiceImpl implements SeckillGoodsListCacheService {
    private final LocalCacheService<Long, SeckillBusinessCache<List<SeckillGoods>>> localCacheService;
    private final DistributedCacheService distributedCacheService;
    private final SeckillGoodsDomainService seckillGoodsDomainService;
    private final DistributedLockFactory distributedLockFactory;
    /**
     * 本地锁
     */
    private final Lock localCacheUpdatelock = new ReentrantLock();

    @DubboReference
    private SeckillStockDubboService seckillStockDubboService;
    @Value("${place.order.type:lua}")
    private String placeOrderType;
    @Override
    public String buildCacheKey(Object key) {
        return  RedisConstants.SECKILL_GOODSES_CACHE_KEY + key;
    }

    @Override
    public String buildLockKey(Object key) {
        return RedisConstants.SECKILL_GOODSES_UPDATE_CACHE_LOCK_KEY + key;
    }

    @Override
    public SeckillBusinessCache<List<SeckillGoods>> getCachedGoodsList(Long activityId, Long version) {
        // 1.获取本地缓存
        SeckillBusinessCache<List<SeckillGoods>> seckillGoodsListLocalCache = localCacheService.getIfPresent(activityId);
        if (seckillGoodsListLocalCache != null){
            // 1.1.如果版本号为空，直接返回
            if (version == null){
                log.info("SeckillGoodsListCache|命中本地缓存:[版本号为null]|活动id:{}", activityId);
                return seckillGoodsListLocalCache;
            }
            // 1.2.传递过来的版本小于或等于缓存中的版本号，直接返回
            if (version.compareTo(seckillGoodsListLocalCache.getVersion()) <= 0){
                log.info("SeckillGoodsListCache|命中本地缓存:[版本号<缓存版本号]-活动id:{}", activityId);
                return seckillGoodsListLocalCache;
            }
            if (version.compareTo(seckillGoodsListLocalCache.getVersion()) > 0){
                log.info("SeckillGoodsListCache|命中本地缓存:[版本号>缓存版本号]-活动id:{}", activityId);
                return getDistributedCache(activityId);
            }
        }
        log.info("SeckillGoodsListCache|未命中本地缓存|活动id:{}", activityId);
        // 2.获取分布式缓存
        return getDistributedCache(activityId);
    }

    private SeckillBusinessCache<List<SeckillGoods>> getDistributedCache(Long activityId) {
        log.info("SeckillGoodsListCache|读取分布式缓存|活动id:{}", activityId);
        // 2.1.查询分布式缓存
        SeckillBusinessCache<List<SeckillGoods>> seckillgoodsListDistributeCache = distributedCacheService.getGenericObject(buildCacheKey(activityId), new TypeReference<SeckillBusinessCache<List<SeckillGoods>>>() {
        });
        if (seckillgoodsListDistributeCache == null){
            // 2.2.分布式缓存为空，尝试使用1个线程更新分布式缓存中的数据
            log.info("SeckillGoodsListCache|分布式缓存:[未命中]|活动id:{}", activityId);
            seckillgoodsListDistributeCache = tryUpdateSeckillGoodsCacheByLock(activityId, true);
        }
        if (seckillgoodsListDistributeCache != null && !seckillgoodsListDistributeCache.isRetryLater()){
            // 2.3.分布式缓存不为空，且不需要进行重试
            if (localCacheUpdatelock.tryLock()){
                try {
                    localCacheService.put(activityId, seckillgoodsListDistributeCache);
                    log.info("SeckillGoodsListCache|本地缓存:[更新完成]|活动id:{}", activityId);
                }finally {
                    localCacheUpdatelock.unlock();
                }
            }
        }
        return seckillgoodsListDistributeCache;
    }

    @Override
    public SeckillBusinessCache<List<SeckillGoods>> tryUpdateSeckillGoodsCacheByLock(Long activityId, boolean doubleCheck) {
        String lockKey = buildLockKey(activityId);
        long start = System.currentTimeMillis();
        DistributedLock lock = distributedLockFactory.getDistributedLock(lockKey);
        try {
            boolean isLockSuccess = lock.tryLock(100, 1000, TimeUnit.MILLISECONDS);
            if (!isLockSuccess){
                // 没有获取到分布式锁，返回重试数据模型
                return new SeckillBusinessCache<List<SeckillGoods>>().retryLater();
            }
            log.info("SeckillGoodsListCache|分布式缓存:[更新中]|活动id{}", activityId);
            SeckillBusinessCache<List<SeckillGoods>> seckillGoodsListDistributeCache;
            //获取锁成功后，再次从缓存中获取数据，防止高并发下多个线程争抢锁的过程中，后续的线程再等待1秒的过程中，前面的线程释放了锁，后续的线程获取锁成功后再次更新分布式缓存数据
            if (doubleCheck){
                seckillGoodsListDistributeCache = distributedCacheService.getGenericObject(buildCacheKey(activityId), new TypeReference<SeckillBusinessCache<List<SeckillGoods>>>() {
                });
                if (seckillGoodsListDistributeCache != null){
                    return seckillGoodsListDistributeCache;
                }
            }
            List<SeckillGoods> seckillGoodsList = seckillGoodsDomainService.getSeckillGoodsByActivityId(activityId);
            if (CollUtil.isEmpty(seckillGoodsList)){
                // 查询的数据为空，返回空数据模型
                seckillGoodsListDistributeCache = new SeckillBusinessCache<List<SeckillGoods>>().notExist();
            }else {
                // 数据存在，返回数据+版本号模型
                seckillGoodsList = this.getSeckillGoodsList(seckillGoodsList);
                seckillGoodsListDistributeCache = new SeckillBusinessCache<List<SeckillGoods>>().with(seckillGoodsList).withVersion(SystemClock.millisClock().now());
            }
            distributedCacheService.put(buildCacheKey(activityId), seckillGoodsListDistributeCache, RedisConstants.FIVE_MINUTES);
            log.info("SeckillGoodsListCache|分布式缓存:[更新成功]-活动id{}", activityId);
            return seckillGoodsListDistributeCache;
        }catch (InterruptedException e){
            // 分布式缓存更新失败，返回重试模型
            log.info("SeckillGoodsListCache|分布式缓存:[更新失败]-活动id{}", activityId);
            return new SeckillBusinessCache<List<SeckillGoods>>().retryLater();
        }finally {
            log.info("{}释放锁，持有时间:{}",Thread.currentThread(), System.currentTimeMillis() - start);
            lock.unlock();
        }
    }

    private List<SeckillGoods> getSeckillGoodsList(List<SeckillGoods> seckillGoodsList) {
        if (!PlaceOrderType.BUCKET.getType().equals(placeOrderType)){
            return seckillGoodsList;
        }
        return seckillGoodsList.stream().map(this::getSeckillGoods).collect(Collectors.toList());
    }

    //兼容分桶库存
    private SeckillGoods getSeckillGoods(SeckillGoods seckillGoods){
        //不是分桶库存模式
        if (!PlaceOrderType.BUCKET.getType().equals(placeOrderType)){
            return seckillGoods;
        }
        //是分桶库存模式，获取分桶库存
        SeckillBusinessCache<SeckillStockDTO> seckillStockCache = seckillStockDubboService.getSeckillStock(seckillGoods.getId(), 1L);
        if (seckillStockCache == null || !seckillStockCache.isExist() || seckillStockCache.isRetryLater() || seckillStockCache.getData() == null){
            return seckillGoods;
        }
        seckillGoods.setInitialStock(seckillStockCache.getData().getTotalStock());
        seckillGoods.setAvailableStock(seckillStockCache.getData().getAvailableStock());
        return seckillGoods;
    }
}
